//@version=6
indicator("Savitzky-Golay Hampel Filter | AlphaNatt", overlay=true, shorttitle = "SGHF | AlphaNatt")

// Inputs
polyOrder = input.int(2, "Polynomial Order", minval=2, maxval=5, tooltip="Order of polynomial fitting (2=quadratic, 3=cubic)")
windowSize = input.int(21, "Window Size", minval=7, maxval=51, step=2, tooltip="Must be odd number")
hampelThreshold = input.float(3.0, "Hampel Threshold", minval=1.0, maxval=5.0, step=0.5, tooltip="MAD multiplier for outlier detection")
smoothingFactor = input.int(3, "Final Smoothing", minval=1, maxval=7)

// Ensure window size is odd
window = windowSize % 2 == 0 ? windowSize + 1 : windowSize
halfWindow = math.floor(window / 2)

// Savitzky-Golay Filter Implementation
// This filter fits a polynomial to data points and evaluates at the center
// Used by NASA for satellite data processing and by chemists for spectroscopy

// Simplified SG coefficients for 3rd order polynomial, 21-point window
// These are precomputed convolution coefficients
getSGCoeff(i, order, window_) =>
    // Simplified coefficient calculation
    center = math.floor(window_ / 2)
    norm = window_ * (window_ * window_ - 1) / 12
    
    if order == 2  // Quadratic
        coeff = -center + i
        weight = 3 * window_ * (window_ + 1) - 7 - 20 * coeff * coeff
        weight / (4 * norm)
    else if order == 3  // Cubic
        coeff = i - center
        h = coeff * coeff
        weight = (315 + h * (-420 + h * 48)) / 320
        weight
    else  // Higher order approximation
        1.0 / window_

// Apply Savitzky-Golay Filter
sgFilter = 0.0
sumWeights = 0.0

for i = 0 to window - 1
    idx = i - halfWindow
    weight = getSGCoeff(i, polyOrder, window)
    sgFilter += nz(close[math.abs(idx)]) * weight
    sumWeights += math.abs(weight)

sgFilter := sumWeights != 0 ? sgFilter / sumWeights : close

// Hampel Filter for Outlier Detection
// Used in robust statistics to identify and replace outliers
// Based on Median Absolute Deviation (MAD)

// Calculate median of recent values
medianArray = array.new_float(window)
for i = 0 to window - 1
    array.set(medianArray, i, nz(close[i], close))

median = array.median(medianArray)

// Calculate MAD (Median Absolute Deviation)
madArray = array.new_float(window)
for i = 0 to window - 1
    val = nz(close[i], close)
    array.set(madArray, i, math.abs(val - median))

mad = array.median(madArray)
mad := mad == 0 ? 0.001 : mad  // Prevent division by zero

// Detect outliers using Hampel identifier
isOutlier = math.abs(close - median) > hampelThreshold * 1.4826 * mad

// Replace outliers with SG filtered value
cleanedPrice = isOutlier ? sgFilter : close

// Apply secondary Savitzky-Golay pass on cleaned data
sgFinal = 0.0
for i = 0 to window - 1
    idx = i - halfWindow
    weight = getSGCoeff(i, polyOrder, window)
    price = isOutlier[math.abs(idx)] ? sgFilter[math.abs(idx)] : nz(close[math.abs(idx)], close)
    sgFinal += price * weight

sgFinal := sumWeights != 0 ? sgFinal / sumWeights : cleanedPrice

// Final smoothing with weighted moving average
finalFilter = ta.wma(sgFinal, smoothingFactor)

// Calculate derivative for trend detection (SG filters preserve derivatives)
firstDerivative = finalFilter - finalFilter[1]
secondDerivative = firstDerivative - firstDerivative[1]

// Advanced trend detection
trendStrength = math.abs(firstDerivative) / ta.atr(14) * 100
accelerating = secondDerivative > 0

// Signal logic
strongTrend = trendStrength > 1.0
priceAbove = close > finalFilter
rising = finalFilter > finalFilter[1] and finalFilter[1] > finalFilter[2]

bullish = (rising and priceAbove) or (rising and strongTrend)
bearish = not rising or not priceAbove

// Signal Color
signalColor = bullish ? #00F1FF : #FF019A

// Plot
plot(finalFilter, "Savitzky-Golay Hampel Filter", signalColor, 2)

// Alerts
alertcondition(ta.crossover(close, finalFilter) and strongTrend, "SG-Hampel Bull Cross", "Price crossed above filter with strong trend")
alertcondition(ta.crossunder(close, finalFilter) and strongTrend, "SG-Hampel Bear Cross", "Price crossed below filter with strong trend")